classdef InteractionLogic < PlatformLogic & VideoLogic & AnalogInLogic

  properties
    AvailableStates = {'Starting','Stopping','Ready','Active','Busy','Waiting','TrialActive','Saving'};
  end
    
  methods
    % CONSTRUCTOR
    function O = InteractionLogic(varargin)
      global CG;
      % Call Superclass constructors
      O@PlatformLogic; 
      O@VideoLogic; 
      O@AnalogInLogic(...
        'Device',CG.Parameters.Setup.Audio.Device{2},...
        'SRAI',CG.Parameters.Setup.Audio.SRAI,...
        'ChAI',CG.Parameters.Setup.Audio.ChAI,...
        'ChDO',[],'ChAO',[],...
        'TerminalConfig','Differential',...
        'AINames',{'MicLeft','MicRight'},...
        'ControlInterval',0.25,...
        'Name','Audio',...
        'Saving','All');
      
      O = setName(O,'Interaction');
      P = parsePairs(varargin);
      O.ParametersFull = {...
        'ControlInterval', 0.1,'Numeric',inf;...
        'TrialDurationMax',     120,            'Numeric',inf;...
        'InteractionPartner', 'mouse1','String','TMP = C_getAnimals;';...
        'DistInit',                     70,            'Numeric',inf};
            
      % HW RELATED
      O.HW.ArduinoIDs = [O.HW.IDs.ArduinoPlat,O.HW.IDs.ArduinoCam];
      O.HW.NArduinos = length(O.HW.ArduinoIDs);
     
      % ANIMAL TRACKING (Animal Position on the grid: [-3,-2.5,-2,-1.5,-1,0,1,1.5,2,2.5,3];)
      O.States.AnimalPositionY = [-9:0];
      O.States.AnimalPositionX = [linspace(-3,-1,O.HW.NAnimalSensors-1),0,linspace(1,3,O.HW.NAnimalSensors-1)];
      O.States.AnimalPosition = zeros(length(O.States.AnimalPositionY),length(O.States.AnimalPositionX)); % Tracks the animal position over time
      O.States.InputStates = zeros(1,length(O.HW.Inputs));
     
      % DISPLAY
      O.Display.LastFigureUpdate = now*CG.Misc.DateNum2SecFactor; % Time when the last figure was updated
      O.assignParameters;
    end
    
    % START THE PARADIGM
    function start(O)
      global CG;
      C_setDigitalNI(O.HW.IDs.DAQ,'Trial',0);
      C_setDigitalNI(O.HW.IDs.DAQ,'Camera',0);
      start@Logic(O) % can be used to use the subclass and the class function
      % INITIALIZE THE SYSTEM
      O.assignParameters;
      O.States.AnimalPosition(:) = 0;
      O.startAcquisition(O.HW.IDs.DAQ);
      % SHOW FIGURES
       for iM=1:length(O.Modules)
        eval(['C_show',O.Modules(iM).Type,'(',n2s(O.Modules(iM).IDType),',0);']);
       end
       
      %INITIALIZE SETUP
      pause(1.5); % wait for Arduino to boot
      O.moveGates([1,2],'closed');
      O.prepareDisplay;  O.showDisplay(1);
      O.updateStates;
      O.initializePlatforms;
      O.initializeCamera;
      O.createMemoryTimer;
      start(O.Timers.LowMemory);
        
      % PREPARE FOR FIRST TRIAL
     O.prepareTrial;
      pauseUntil(O.TimeToContinue);
      Choice = questdlg('Please put the animals on the platforms (Male : Left, Female : Right)',...
        'Animal Dialog','Continue','Break','Continue');
      switch Choice;  case 'Break'; return; end
      O.startTrial;
    end
    
    function stop(O)
      O.stopTrial;
      O.moveGates([1,2],'closed');
      O.deleteMemoryTimer;
      O.deleteTimers;
             
      stop@Logic(O);
    end
    
    % PREPARE THE NEXT TRIAL
    function prepareTrial(O)
      global CG;
      
      % PREPARE FILES AND INCREASE TRIAL
      prepareTrial@Logic(O);
 
      if O.Trial == 1
        O.computeTrialParameters;
        for iP=1:2 
          PlatformDistance(iP) = O.Trials(O.Trial).PlatformPosition(iP) - O.HW.Platforms(iP).Position;
          cPausePlatform(iP) = PlatformDistance(iP)*O.HW.Platforms(iP).TimePerMM;
        end
        CameraDistance =  abs(O.Trials(O.Trial).CameraPosition - O.HW.Camera(1).Position);
        cPauseCamera = CameraDistance*O.HW.Camera(1).TimePerMM;
        cPause = max([cPausePlatform,cPauseCamera]);

        for iP = 1:2 
          O.movePlatform(iP,PlatformDistance(iP)); pause(0.1);
        end
        O.moveCamera(O.HW.Camera.CameraID,CameraDistance);
      else
        cPause = 0;
      end
      O.TimeToContinue = now*86400 + cPause;
      
      O.prepareVideo(O.HW.IDs.Camera);
      O.prepareAnalogIn(O.HW.IDs.AnalogIn);
      
    end
     
    function startTrial(O)
      O.Trials(O.Trial).Attempt = 0;
      O.updateDisplay;
      
      O.startAnalogIn(O.HW.IDs.AnalogIn);
      
       % START VIDEO IF ANIMALS ALREADY AT THE FRONT GATE
      States = O.updateStates;   O.updateAnimalPosition;
      Animal1 = O.getInputByName(['AniPosP1S3']);
      Animal2 = O.getInputByName(['AniPosP2S3']);
      if ~States(Animal1) && ~States(Animal2);      O.startVideo(O.HW.IDs.Camera);    end
      
      % SEND TRIAL START SIGNAL
       startTrial@Logic(O);
       C_setDigitalNI(O.HW.IDs.DAQ,'Trial',1);
       disp(['> Starting Trial ',num2str(O.Trial)]);
       O.moveGates([1,2],'open');
    end
    
    % STOP THE CURRENT TRIAL
    function stopTrial(O)
      O.TrialActive = 0; O.changeState('Busy');
      
      % PAUSE VIDEO ACQUISITION
      SaveVideo = O.VideoActive; 
      if O.VideoActive;      O.pauseVideo(O.HW.IDs.Camera);     end
     
      % CLOSE GATE
      if O.VideoActive  O.stopVideo(O.HW.Camera.CameraID); end
      O.moveGates([1,2],'closed');
      
      % STOP AUDIO ACQUISITION
      SaveAnalogIn = O.AnalogInActive; 
      if O.AnalogInActive O.stopAnalogIn(O.HW.IDs.AnalogIn); end
      
      % WRAP UP TRIAL
      O.stopTrialTimer;
      stopTrial@Logic(O);
      disp('stopTrial finished.')
    end
    
      % COMPUTE NEW POSITIONS OF PLATFORMS & CAMERA
    function computeTrialParameters(O)
      global CG;
      
      % COMPUTE POSITIONS FOR PLATFORMS
      O.Trials(O.Trial).PlatformPosition(1:2) = O.Parameters.DistInit/2 - [CG.Parameters.Setup.Platforms.PositionOffset];
      if sum(O.Trials(O.Trial).PlatformPosition(1:2) < 0)
        error('Distance DistInit between Platforms cannot be less than the sum of the minimal distances of the platform.'); 
      end
      O.Trials(O.Trial).Distance = O.Parameters.DistInit;
      
      % COMPUTE CAMERA POSITION
      NewCamPos = O.HW.Camera.DistanceToCenter;  
      if NewCamPos < 0 fprintf('Camera position below lower limit!\n'); keyboard; end
      if NewCamPos > O.HW.Camera(1).RangeMax;
        fprintf('Camera position above upper range limit!\n'); keyboard; end
      O.Trials(O.Trial).CameraPosition = NewCamPos;
    end
    
    % UPDATE ANIMAL POSITION
    function updateAnimalPosition(O)
       InputStates = O.States.InputStates(O.HW.AnimalSensorsPhys);
      InputStates = ~InputStates;
      if sum(InputStates) % IF AT LEAST ONE SENSOR IS ACTIVE
        O.States.AnimalPosition(end+1,[1:2:end]) = InputStates/sum(InputStates);
      else % NO SENSOR ACTIVE
        if max(O.States.AnimalPosition(end,:))==1 % 
          T = zeros(1,size(O.States.AnimalPosition,2));
          for i=1:size(O.States.AnimalPosition,2)
            C1 = 0; if i>1; C1 = O.States.AnimalPosition(end,i-1); end
            C2 = 0; if i<size(O.States.AnimalPosition,2);  C2 = O.States.AnimalPosition(end,i+1); end
            T(i) = C1 + C2;
          end
          O.States.AnimalPosition(end+1,:) = T/sum(T);
        end
      end
      O.States.EstAnimalPosition = sum(O.States.AnimalPositionX.*O.States.AnimalPosition(end,:));
      if O.States.EstAnimalPosition<0; PID = 1; end
      if O.States.EstAnimalPosition>0; PID = 2; end;
      if O.States.EstAnimalPosition==0;  PID = 0; end;
    end
        
    % EVENT PROCESSING (CORE FUNCTION)
    function processEvent(O,SourceName,Event)
      if ~O.ParadigmActive return; end
      switch SourceName
        case 'Keyboard';
          processEvent@Logic(O,SourceName,Event);
          
        case 'NIDAQ';
         InputStates = O.updateStates(Event.Time(1));
          O.updateAnimalPosition;
          O.updateDisplay;
          switch Event.Name
            case 'AI_to_High'; % Thresholded change of analog channels
              switch Event.Data % Channel Number that has changed
                case {1,5}; % Outer End
                case {2,6}; % Middle
                case {3,7}; % Inner End
                  if O.TrialActive
                    disp(['Mouse ',num2str(ceil((Event.Data-1)/4)),' has left the gap.']);
                    if  O.VideoActive
                      O.pauseVideo(1);
                      O.prepareVideo(1);
                    end
                    %  O.TrialActive = 0;
                    %  C_setDigitalNI(O.HW.IDs.DAQ,'Trial',0);
                    %                     evalin('base',['T= timer(''TimerFcn'',[''global CG; '...
                    %                       'CG.Paradigm.stopTrial; '...
                    %                       'CG.Paradigm.prepareTrial; pauseUntil(CG.Paradigm.TimeToContinue); '...
                    %                       'CG.Paradigm.startTrial;''],'...
                    %                       '''StartDelay'',0.1,'...
                    %                       '''Name'',''AnimalLeftGap''); '...
                    %                       'start(T);'])

                  end
              end % END SWITCH EVENT NUMBER
              
            case 'AI_to_Low';  % Thresholded change of analog channels
              switch Event.Data % Channel Number that has changed
                case {1,5}; % Outer End.
              
                case {2,6}; % Middle
                  
                case {3,7}; % Inner End
                  if O.TrialActive
                    % CHECK WHETHER OPPOSITE SENSOR IS COVERED ALREADY
                    State1 = InputStates(O.getInputByName(['AniPosP1S3']));
                    State2 = InputStates(O.getInputByName(['AniPosP2S3']));                    
                    disp(['Mouse ',num2str(ceil((Event.Data-1)/4)),' is at the gap.']);
                    if State1 == 0 && State2 == 0 % BOTH ANIMALS AT THE GAP, START THE VIDEO
                      disp('Both Animals at the Gap.'); 
                      if ~O.VideoActive && O.VideoReady
                        O.startVideo(O.HW.IDs.Camera);
                        disp('Video Starting!');
                      end
                    end
                  end
              end
          end % END SWITCH EVENT TYPE
      end
    end
     
    % PREPARE DISPLAY BEFORE FIRST PLOT
    function prepareDisplay(O)
      if ~isfield(O.Display,'Handles') || ~ishandle(O.Display.Handles.FIG)
        prepareDisplay@Logic(O); 
      else
        figure(O.Display.Handles.FIG); 
      end
      clf;
      DC = axesDivide(1,[1],[0.1,0.2,0.8,0.7],[],1);
      colormap(HF_colormap({[1,1,1],[1,0,0]},[0,1]));
      % ANIMAL POSITION
      FontSize = 8;
      O.Display.Handles.AnimalAxis = axes('Pos',DC{1},'FontSize',FontSize);
      O.Display.Handles.AnimalPosPlot = imagesc(O.States.AnimalPositionX,O.States.AnimalPositionY,O.States.AnimalPosition(end-9:end,:));
      set(O.Display.Handles.AnimalAxis,'CLim',[0,1],'XLim',[O.States.AnimalPositionX([1,end])],'YLim',O.States.AnimalPositionY([1,end]));
      O.Display.Handles.AnimalTitle = get(O.Display.Handles.AnimalAxis,'Title');
      set(O.Display.Handles.AnimalTitle,'String',['Animal Position']);
            
      % CURRENT TRIAL INFORMATION
      % text fields which contain the information 
      Fields = {'Distance'};
      for iF=1:length(Fields)
        O.Display.Handles.([Fields{iF},'Text']) = text(0.5+(iF-2)*0.4,-0.8,[Fields{iF}],'Units','norm','FontSize',8,'Horiz','center');
      end
      O.Display.LastFigureUpdate = now;
      set(O.Display.Handles.FIG,'Visible','off');
      
%      uicontrol('style','pushbutton','String','','Units','norm','Pos',DC{2},'Callback','global CG; CG.Paradigm.stop;');
      
    end
    
    % UPDATE FIGURE WHENEVER NECESSARY
    function updateDisplay(O)
      global CG
      cTime = now*CG.Misc.DateNum2SecFactor;
      % LIMIT UPDATE RATE TO CONTROLINTERVAL
      if get(CG.GUI.Main.Paradigm.ShowButton,'Value') ...
          && cTime - O.Display.LastFigureUpdate > O.Parameters.ControlInterval;
        
        % ANIMAL POSITION
        set(O.Display.Handles.AnimalPosPlot,'CData',O.States.AnimalPosition(end-9:end,:));

        O.Display.LastFigureUpdate = cTime;
      end
    end
  end
end